package pe.com.bbva.ge.persistencia.orm.hibernate;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import pe.com.bbva.ge.domain.configuracion.Posicion;
import pe.com.bbva.ge.domain.configuracion.Valor;
import pe.com.bbva.ge.domain.solicitud.Destino;
import pe.com.bbva.ge.domain.solicitud.DetalleCasetera;
import pe.com.bbva.ge.domain.solicitud.DetalleDenominacion;
import pe.com.bbva.ge.domain.solicitud.DetalleMaterial;
import pe.com.bbva.ge.domain.solicitud.DetalleMoneda;
import pe.com.bbva.ge.domain.solicitud.DetalleOtroServicio;
import pe.com.bbva.ge.domain.solicitud.EstadoSolicitud;
import pe.com.bbva.ge.domain.solicitud.Remito;
import pe.com.bbva.ge.domain.solicitud.Solicitud;
import pe.com.bbva.ge.domain.solicitud.TipoSolicitud;
import pe.com.bbva.ge.persistencia.orm.SolicitudORM;
import pe.com.bbva.ge.persistencia.orm.hibernate.utileria.Busqueda;
import pe.com.bbva.ge.persistencia.orm.hibernate.utileria.QueryParametro;
import pe.com.bbva.ge.persistencia.orm.hibernate.utileria.QueryParametro.TipoQueryParametro;
import pe.com.bbva.ge.utileria.form.BandejaTrabajoForm;

@Repository
public class SolicitudORMHibernate extends BaseORMHibernate implements SolicitudORM {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1069924240377681947L;

	@Autowired
	public SolicitudORMHibernate(SessionFactory sessionFactory) {
		super(sessionFactory);
	}
	
	public List<Solicitud> obtenerSolicitudesPendientes1(BandejaTrabajoForm bandejaTrabajoForm)
	{
		
		Set<QueryParametro<?>> parametros = new HashSet<QueryParametro<?>>();		
		
		StringBuilder sql = new StringBuilder();
		sql.append("select distinct dm.destino.solicitud from DetalleMoneda dm ");//FIXME Cambio de Nombre
		sql.append("where ");
		
		sqlComunBandejaTrabajo(sql, parametros, bandejaTrabajoForm);
		
		//Consulta Divisa
		Map<String, BigDecimal[]> divisas = bandejaTrabajoForm.getMapDivisas();
		if(!divisas.isEmpty())
		{
			Iterator<String> llaves = divisas.keySet().iterator();
			int i = 1;
			while (llaves.hasNext()) {
				String llave = llaves.next();
				
				QueryParametro<String> divisa = new QueryParametro<String>();
				divisa.setCodigo("divisa"+i);
				divisa.setTipo(TipoQueryParametro.STRING);
				divisa.setValor(llave);
				parametros.add(divisa);					
				
				//total
				sql.append("and dm.moneda.codigo = (:").append(divisa.getCodigo()).append(") ");//FIXME cmabio de nombre
				
				BigDecimal[] valores = divisas.get(llave);
				if(valores[0] != null && 
						valores[1]== null)
				{
					QueryParametro<BigDecimal> total1 = new QueryParametro<BigDecimal>();
					total1.setCodigo("total1"+i);
					total1.setTipo(TipoQueryParametro.BIG_DECIMAL);
					total1.setValor(valores[0]);
					parametros.add(total1);	
					
					sql.append("and dm.total > :").append(total1.getCodigo()).append(" ");
				}
				else if(valores[0] == null && 
						valores[1] != null)
				{
					QueryParametro<BigDecimal> total2 = new QueryParametro<BigDecimal>();
					total2.setCodigo("total2"+i);
					total2.setTipo(TipoQueryParametro.BIG_DECIMAL);
					total2.setValor(valores[1]);
					parametros.add(total2);
					
					sql.append("and dm.total < :").append(total2.getCodigo()).append(" ");
				}
				else
				{
					QueryParametro<BigDecimal> total1 = new QueryParametro<BigDecimal>();
					total1.setCodigo("total1"+i);
					total1.setTipo(TipoQueryParametro.BIG_DECIMAL);
					total1.setValor(valores[0]);
					parametros.add(total1);	
					
					QueryParametro<BigDecimal> total2 = new QueryParametro<BigDecimal>();
					total2.setCodigo("total2"+i);
					total2.setTipo(TipoQueryParametro.BIG_DECIMAL);
					total2.setValor(valores[1]);
					parametros.add(total2);
					
					sql.append("and dm.total between :").append(total1.getCodigo()).append(" and :").append(total2.getCodigo()).append(" ");
				}
				i++;
			}
		}
		
		return ejecutarConsultaBandejaTrabajo(sql, parametros);
	}
	
	@Override
	public List<Solicitud> obtenerSolicitudesPendientes2(
			BandejaTrabajoForm bandejaTrabajoForm) {
		
		Set<QueryParametro<?>> parametros = new HashSet<QueryParametro<?>>();		
		
		StringBuilder sql = new StringBuilder();
		sql.append("select distinct dm.destino.solicitud from DetalleMaterial dm ");//FIXME Cambio de Nombre
		sql.append("where ");
		
		sqlComunBandejaTrabajo(sql, parametros, bandejaTrabajoForm);		
		
		return ejecutarConsultaBandejaTrabajo(sql, parametros);
	}

	@Override
	public List<Solicitud> obtenerSolicitudesPendientes3(
			BandejaTrabajoForm bandejaTrabajoForm) {
		
		Set<QueryParametro<?>> parametros = new HashSet<QueryParametro<?>>();		
		
		StringBuilder sql = new StringBuilder();
		sql.append("select distinct dm.destino.solicitud from DetalleOtroServicio dm ");//FIXME Cambio de Nombre
		sql.append("where ");
		
		sqlComunBandejaTrabajo(sql, parametros, bandejaTrabajoForm);		
		
		return ejecutarConsultaBandejaTrabajo(sql, parametros);
	}
	
	private void sqlComunBandejaTrabajo(StringBuilder sql, Set<QueryParametro<?>> parametros,
			BandejaTrabajoForm bandejaTrabajoForm) {
		
		// Quitando estados cerrados
		QueryParametro<List<String>> estadosCerrados = new QueryParametro<List<String>>();
		estadosCerrados.setCodigo("estadosCerrados");
		estadosCerrados.setTipo(TipoQueryParametro.LIST);
		estadosCerrados.setValor(new ArrayList<String>());

		estadosCerrados.getValor().add(EstadoSolicitud.CODIGO.ATENDIDO);
		estadosCerrados.getValor().add(EstadoSolicitud.CODIGO.RECHAZADO);

		parametros.add(estadosCerrados);
		sql.append("dm.destino.solicitud.estado.codigo not in (:")
				.append(estadosCerrados.getCodigo()).append(") ");

		// Consulta por numero de Solicitud segun los dos parametros de entrada.
		if (StringUtils.isNotEmpty(bandejaTrabajoForm.getNumeroSolicitud1())
				|| StringUtils.isNotEmpty(bandejaTrabajoForm
						.getNumeroSolicitud2())) {
			if (StringUtils
					.isNotEmpty(bandejaTrabajoForm.getNumeroSolicitud1())
					&& StringUtils.isEmpty(bandejaTrabajoForm
							.getNumeroSolicitud2())) {
				QueryParametro<String> numeroSolicitud1 = new QueryParametro<String>();
				numeroSolicitud1.setCodigo("numeroSolicitud1");
				numeroSolicitud1.setTipo(TipoQueryParametro.STRING);
				numeroSolicitud1.setValor(bandejaTrabajoForm
						.getNumeroSolicitud1());
				sql.append("and dm.destino.solicitud.numero > :")
						.append(numeroSolicitud1.getCodigo()).append(" ");
				parametros.add(numeroSolicitud1);
			} else if (StringUtils.isEmpty(bandejaTrabajoForm
					.getNumeroSolicitud1())
					&& StringUtils.isNotEmpty(bandejaTrabajoForm
							.getNumeroSolicitud2())) {
				QueryParametro<String> numeroSolicitud2 = new QueryParametro<String>();
				numeroSolicitud2.setCodigo("numeroSolicitud1");
				numeroSolicitud2.setTipo(TipoQueryParametro.STRING);
				numeroSolicitud2.setValor(bandejaTrabajoForm
						.getNumeroSolicitud2());
				sql.append("and dm.destino.solicitud.numero < :")
						.append(numeroSolicitud2.getCodigo()).append(" ");
				parametros.add(numeroSolicitud2);
			} else {
				QueryParametro<String> numeroSolicitud1 = new QueryParametro<String>();
				numeroSolicitud1.setCodigo("numeroSolicitud1");
				numeroSolicitud1.setTipo(TipoQueryParametro.STRING);
				numeroSolicitud1.setValor(bandejaTrabajoForm
						.getNumeroSolicitud1());
				parametros.add(numeroSolicitud1);

				QueryParametro<String> numeroSolicitud2 = new QueryParametro<String>();
				numeroSolicitud2.setCodigo("numeroSolicitud1");
				numeroSolicitud2.setTipo(TipoQueryParametro.STRING);
				numeroSolicitud2.setValor(bandejaTrabajoForm
						.getNumeroSolicitud2());
				parametros.add(numeroSolicitud2);

				sql.append("and dm.destino.solicitud.numero between :")
						.append(numeroSolicitud1.getCodigo()).append(" and :")
						.append(numeroSolicitud2.getCodigo()).append(" ");
			}
		}

		// Consulta por tipo de solicitud
		if (StringUtils.isNotEmpty(bandejaTrabajoForm.getTipoSolicitud())) {

			QueryParametro<String> tipoSolicitud = new QueryParametro<String>();
			tipoSolicitud.setCodigo("tipoSolicitud");
			tipoSolicitud.setTipo(TipoQueryParametro.STRING);
			tipoSolicitud.setValor("%" + bandejaTrabajoForm.getTipoSolicitud()
					+ "%");
			parametros.add(tipoSolicitud);

			sql.append("and (dm.destino.solicitud.tipo.codigo like :")
					.append(tipoSolicitud.getCodigo()).append(" ");
			sql.append("or dm.destino.solicitud.tipo.nombre like :")
					.append(tipoSolicitud.getCodigo()).append(") ");
		}

		// /Consulta por Origen
		if (StringUtils.isNotEmpty(bandejaTrabajoForm.getOrigen())) {

			QueryParametro<String> origen = new QueryParametro<String>();
			origen.setCodigo("origen");
			origen.setTipo(TipoQueryParametro.STRING);
			origen.setValor("%" + bandejaTrabajoForm.getOrigen() + "%");
			parametros.add(origen);

			sql.append("and (dm.destino.solicitud.origen.codigo like :")
					.append(origen.getCodigo()).append(" ");
			sql.append("or dm.destino.solicitud.origen.nombre like :")
					.append(origen.getCodigo()).append(" ");
			sql.append("or dm.destino.solicitud.tipoOrigen.codigo like :")
					.append(origen.getCodigo()).append(" ");
			sql.append("or dm.destino.solicitud.tipoOrigen.nombre like :")
					.append(origen.getCodigo()).append(") ");
		}

		// /Consulta por Destino
		if (StringUtils.isNotEmpty(bandejaTrabajoForm.getDestino())) {
			QueryParametro<String> destino = new QueryParametro<String>();
			destino.setCodigo("destino");
			destino.setTipo(TipoQueryParametro.STRING);
			destino.setValor("%" + bandejaTrabajoForm.getDestino() + "%");
			parametros.add(destino);

			sql.append("and (dm.destino.destino.codigo like :")
					.append(destino.getCodigo()).append(" ");
			sql.append("or dm.destino.destino.nombre like :")
					.append(destino.getCodigo()).append(" "); // FIXME Cambio de
																// Nombre
			sql.append("or dm.destino.tipo.codigo like :")
					.append(destino.getCodigo()).append(" ");
			sql.append("or dm.destino.tipo.nombre like :")
					.append(destino.getCodigo()).append(") ");
		}

		// /Consulta por Estado
		if (StringUtils.isNotEmpty(bandejaTrabajoForm.getEstado())) {
			QueryParametro<String> estado = new QueryParametro<String>();
			estado.setCodigo("estado");
			estado.setTipo(TipoQueryParametro.STRING);
			estado.setValor("%" + bandejaTrabajoForm.getEstado() + "%");
			parametros.add(estado);

			sql.append("and (dm.destino.solicitud.estado.codigo like :")
					.append(estado.getCodigo()).append(" ");
			sql.append("or dm.destino.solicitud.estado.nombre like :")
					.append(estado.getCodigo()).append(") ");
		}

		// /Consulta por fecha de Estado//FIXME implementar la fecha de estado
		// por mientra es la fecha de registro
		if (bandejaTrabajoForm.getFechaEstado1() != null
				|| bandejaTrabajoForm.getFechaEstado2() != null) {
			if (bandejaTrabajoForm.getFechaEstado1() != null
					&& bandejaTrabajoForm.getFechaEstado2() == null) {
				QueryParametro<Date> fechaEstado1 = new QueryParametro<Date>();
				fechaEstado1.setCodigo("fechaEstado1");
				fechaEstado1.setTipo(TipoQueryParametro.DATE_TIME);
				fechaEstado1.setValor(bandejaTrabajoForm.getFechaEstado1());
				parametros.add(fechaEstado1);

				sql.append(
						"and coalesce(dm.destino.solicitud.edicion,dm.solicitud.creacion) > :")
						.append(fechaEstado1.getCodigo()).append(" ");
			} else if (bandejaTrabajoForm.getFechaEstado1() == null
					&& bandejaTrabajoForm.getFechaEstado2() != null) {
				QueryParametro<Date> fechaEstado2 = new QueryParametro<Date>();
				fechaEstado2.setCodigo("fechaEstado2");
				fechaEstado2.setTipo(TipoQueryParametro.DATE_TIME);
				fechaEstado2.setValor(bandejaTrabajoForm.getFechaEstado2());
				parametros.add(fechaEstado2);

				sql.append(
						"and coalesce(dm.destino.solicitud.edicion,d.solicitud.creacion) < :")
						.append(fechaEstado2.getCodigo()).append(" ");
			} else {
				QueryParametro<Date> fechaEstado1 = new QueryParametro<Date>();
				fechaEstado1.setCodigo("fechaEstado1");
				fechaEstado1.setTipo(TipoQueryParametro.DATE_TIME);
				fechaEstado1.setValor(bandejaTrabajoForm.getFechaEstado1());
				parametros.add(fechaEstado1);

				QueryParametro<Date> fechaEstado2 = new QueryParametro<Date>();
				fechaEstado2.setCodigo("fechaEstado2");
				fechaEstado2.setTipo(TipoQueryParametro.DATE_TIME);
				fechaEstado2.setValor(bandejaTrabajoForm.getFechaEstado2());
				parametros.add(fechaEstado2);

				sql.append(
						"and coalesce(dm.destino.solicitud.edicion,d.solicitud.creacion) between :")
						.append(fechaEstado1.getCodigo()).append(" and :")
						.append(fechaEstado2.getCodigo()).append(" ");
			}
		}
	}
	
	private List<Solicitud> ejecutarConsultaBandejaTrabajo(StringBuilder sql, Set<QueryParametro<?>> parametros)
	{
		Query query=getSession().createQuery(sql.toString());
		
		Iterator<QueryParametro<?>> iterator = parametros.iterator();
		while (iterator.hasNext()) {
			QueryParametro<?> parametro = iterator.next();
			switch (parametro.getTipo()) {
			case BIG_DECIMAL:
				query.setBigDecimal(parametro.getCodigo(), (BigDecimal) parametro.getValor());
				break;
			case STRING:
				query.setString(parametro.getCodigo(), (String)parametro.getValor());			
				break;
			case LIST:
				query.setParameterList(parametro.getCodigo(), (Collection<String>)parametro.getValor());	
				break;
			case DATE_TIME:
				query.setTimestamp(parametro.getCodigo(), (Date)parametro.getValor());	
				break;
			default:
				break;
			}
			
		}
		
		return (List<Solicitud>) query.list();
	}
	
	public List<Destino> obtenerDestinos(Solicitud solicitud)
	{
		Busqueda filtro = Busqueda.forClass(Destino.class);
		filtro.createAlias("solicitud", "s");
		filtro.add(Restrictions.eq("s.id", solicitud.getId()));
		return buscar(filtro);
	}
	
	public List<DetalleMoneda> obtenerDetalleMonedas(Destino destino)
	{
		Busqueda filtro = Busqueda.forClass(DetalleMoneda.class);
		filtro.createAlias("destino", "d");
		filtro.add(Restrictions.eq("d.id", destino.getId()));
		return buscar(filtro);
	}
	
	public List<DetalleDenominacion> obtenerDetalleDenominaciones(DetalleMoneda detalleMoneda)
	{
		Busqueda filtro = Busqueda.forClass(DetalleDenominacion.class);
		filtro.createAlias("detalleMoneda", "dm");
		filtro.add(Restrictions.eq("dm.id", detalleMoneda.getId()));
		return buscar(filtro);
	}

	//@Override
	public List<DetalleCasetera> obtenerDetalleCasetera(Destino destino) {
		Busqueda filtro = Busqueda.forClass(DetalleCasetera.class);
		filtro.createAlias("destino", "d");
		filtro.add(Restrictions.eq("d.id", destino.getId()));
		return buscar(filtro);
	}
	
	public DetalleMoneda obtenerDetalleMonedaXIdMoneda(Destino destino, Valor moneda)
	{
		Busqueda filtro = Busqueda.forClass(DetalleMoneda.class);
		filtro.createAlias("destino", "d");
		filtro.createAlias("moneda", "m");
		filtro.add(Restrictions.eq("d.id", destino.getId()));
		filtro.add(Restrictions.eq("m.id", moneda.getId()));
		List<DetalleMoneda> listaDetalleMoneda = buscar(filtro);
		
		return listaDetalleMoneda.get(0);
	}

	@Override
	public List<Remito> obtenerRemitos(DetalleMoneda detalleMoneda) {
		Busqueda filtro = Busqueda.forClass(Remito.class);
		filtro.createAlias("detalleMoneda", "d");
		filtro.add(Restrictions.eq("d.id", detalleMoneda.getId()));
		return buscar(filtro);
	}

	@Override
	public DetalleMoneda obtenerDetalleMoneda(Destino destino, Valor moneda) {
		Busqueda filtro = Busqueda.forClass(DetalleMoneda.class);
		filtro.createAlias("destino", "d");
		filtro.createAlias("moneda", "m");
		filtro.add(Restrictions.eq("d.id", destino.getId()));
		filtro.add(Restrictions.eq("m.id", moneda.getId()));
		List<DetalleMoneda> listaDettaleMoneda = buscar(filtro);
		return listaDettaleMoneda.get(0);
	}

	@Override
	public List<DetalleMaterial> obtenerDetalleMateriales(Destino destino) {
		Busqueda filtro = Busqueda.forClass(DetalleMaterial.class);
		filtro.createAlias("destino", "d");
		filtro.add(Restrictions.eq("d.id", destino.getId()));
		return buscar(filtro);
	}

	@Override
	public List<DetalleOtroServicio> obtenerDetalleOtroServicios(Destino destino) {
		Busqueda filtro = Busqueda.forClass(DetalleOtroServicio.class);
		filtro.createAlias("destino", "d");
		filtro.add(Restrictions.eq("d.id", destino.getId()));
		return buscar(filtro);
	}

	@Override
	public List<Destino> obtenerPrimerPedido(TipoSolicitud tipoSolicitud,Posicion posicionDestino) {
		Busqueda filtro = Busqueda.forClass(Destino.class);
		filtro.createAlias("destino", "d");
		filtro.createAlias("solicitud", "s");
		filtro.add(Restrictions.eq("s.id", tipoSolicitud.getId()));
		filtro.add(Restrictions.eq("d.id", posicionDestino.getId()));		
		return buscar(filtro);
	}
}
